<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>linux下使用netlink获取gateway的IP地址 - whowin - 发表我个人原创作品的技术博客</title>
  <meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>

<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />

<meta name="theme-color" content="#f8f5ec" />
<meta name="msapplication-navbutton-color" content="#f8f5ec">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#f8f5ec">


<meta name="author" content="whowin" /><meta name="description" content="要在linux下的程序中获取gateway的IP地址，使用netlink是一种直接、可靠的方法，不需要依赖其它命令，直接从linux内核获取信息，netlink编程的中文资料很少，本文试图用尽可能简单的方式讨论使用netlink获取gataway的IP地址的编程方法，并有大量篇幅介绍netlink的相关数据结构和编程方法，阅读本文需要对linux下编程有一定了解，熟悉IPv4的socket编程；本文对网络编程的初学者有较大难度。
" /><meta name="keywords" content="linux, socket, hugo, dos" />






<meta name="generator" content="Hugo 0.97.3 with theme even" />


<link rel="canonical" href="https://whowin.gitee.io/post/blog/network/0009-get-gateway-ip-via-netlink/" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">



<link href="/sass/main.min.e3fea119b1980e848b03dffbeddb11dd0fba483eed0e5f11870fb8e31f145bbd.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.1.20/dist/jquery.fancybox.min.css" integrity="sha256-7TyXnr2YU040zfSP+rEcz29ggW4j56/ujTPwjMzyqFY=" crossorigin="anonymous">


<meta property="og:title" content="linux下使用netlink获取gateway的IP地址" />
<meta property="og:description" content="要在linux下的程序中获取gateway的IP地址，使用netlink是一种直接、可靠的方法，不需要依赖其它命令，直接从linux内核获取信息，netlink编程的中文资料很少，本文试图用尽可能简单的方式讨论使用netlink获取gataway的IP地址的编程方法，并有大量篇幅介绍netlink的相关数据结构和编程方法，阅读本文需要对linux下编程有一定了解，熟悉IPv4的socket编程；本文对网络编程的初学者有较大难度。" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://whowin.gitee.io/post/blog/network/0009-get-gateway-ip-via-netlink/" /><meta property="article:section" content="post" />
<meta property="article:published_time" content="2023-02-04T16:43:29+08:00" />
<meta property="article:modified_time" content="2023-02-04T16:43:29+08:00" />

<meta itemprop="name" content="linux下使用netlink获取gateway的IP地址">
<meta itemprop="description" content="要在linux下的程序中获取gateway的IP地址，使用netlink是一种直接、可靠的方法，不需要依赖其它命令，直接从linux内核获取信息，netlink编程的中文资料很少，本文试图用尽可能简单的方式讨论使用netlink获取gataway的IP地址的编程方法，并有大量篇幅介绍netlink的相关数据结构和编程方法，阅读本文需要对linux下编程有一定了解，熟悉IPv4的socket编程；本文对网络编程的初学者有较大难度。"><meta itemprop="datePublished" content="2023-02-04T16:43:29+08:00" />
<meta itemprop="dateModified" content="2023-02-04T16:43:29+08:00" />
<meta itemprop="wordCount" content="10912">
<meta itemprop="keywords" content="Linux,Socket,网络编程,netlink,rtnetlink," /><meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="linux下使用netlink获取gateway的IP地址"/>
<meta name="twitter:description" content="要在linux下的程序中获取gateway的IP地址，使用netlink是一种直接、可靠的方法，不需要依赖其它命令，直接从linux内核获取信息，netlink编程的中文资料很少，本文试图用尽可能简单的方式讨论使用netlink获取gataway的IP地址的编程方法，并有大量篇幅介绍netlink的相关数据结构和编程方法，阅读本文需要对linux下编程有一定了解，熟悉IPv4的socket编程；本文对网络编程的初学者有较大难度。"/>

<!--[if lte IE 9]>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/classlist/1.1.20170427/classList.min.js"></script>
<![endif]-->

<!--[if lt IE 9]>
  <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->

  <script async src="/js/busuanzi.pure.mini.js"></script><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9724909319263152"
     crossorigin="anonymous"></script>


</head>
<body>
  <div id="mobile-navbar" class="mobile-navbar">
  <div class="mobile-header-logo">
    <a href="/" class="logo">WhoWin</a>
  </div>
  <div class="mobile-navbar-icon">
    <span></span>
    <span></span>
    <span></span>
  </div>
</div>
<nav id="mobile-menu" class="mobile-menu slideout-menu">
  <ul class="mobile-menu-list">
    <a href="/">
        <li class="mobile-menu-item">首页</li>
      </a><a href="/post/">
        <li class="mobile-menu-item">文章归档</li>
      </a><a href="/article-categories/categories/">
        <li class="mobile-menu-item">文章分类</li>
      </a><a href="/tags/">
        <li class="mobile-menu-item">文章标签</li>
      </a><a href="/about/about/">
        <li class="mobile-menu-item">关于</li>
      </a>
  </ul>

  


</nav>

  <div class="container" id="mobile-panel">
    <header id="header" class="header">
        <div class="logo-wrapper">
  <a href="/" class="logo">WhoWin</a>
  
  <div style="position:absolute; left: 80px; top: 75px; color: crimson">
      ———开源和分享是技术发展的源泉和动力；本博客所有文章均为原创
  </div>
</div>





<nav class="site-navbar">
  <ul id="menu" class="menu">
    <li class="menu-item">
        <a class="menu-item-link" href="/">首页</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/post/">文章归档</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/article-categories/categories/">文章分类</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/tags/">文章标签</a>
      </li><li class="menu-item">
        <a class="menu-item-link" href="/about/about/">关于</a>
      </li>
  </ul>
</nav>

    </header>

    <main id="main" class="main">
      <div class="content-wrapper">
        <div id="content" class="content">
          <article class="post">
    
    <header class="post-header">
      <h1 class="post-title">linux下使用netlink获取gateway的IP地址</h1>

      <div class="post-meta">
        <span class="post-time"> 2023-02-04 </span>
        <div class="post-category">
            <a href="/categories/linux/"> Linux </a>
            <a href="/categories/c-language/"> C Language </a>
            <a href="/categories/network/"> Network </a>
            </div>
        
      </div>
    </header>

    <div class="post-toc" id="post-toc">
  <h2 class="post-toc-title">文章目录</h2>
  <div class="post-toc-content always-active">
    <nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#1-netlink-socket及netlink消息结构">1. netlink socket及netlink消息结构</a></li>
        <li><a href="#2-rtnetlink常用数据结构">2. rtnetlink常用数据结构</a></li>
        <li><a href="#3-netlink编程中常用的宏定义">3. netlink编程中常用的宏定义</a></li>
        <li><a href="#4-rtnetlink编程中常用的宏">4. rtnetlink编程中常用的宏</a></li>
        <li><a href="#5-使用rtnetlink发送请求">5. 使用rtnetlink发送请求</a></li>
        <li><a href="#6-接收和解析内核返回的路由表">6. 接收和解析内核返回的路由表</a></li>
        <li><a href="#7-完整源代码">7. 完整源代码</a></li>
        <li><a href="#欢迎订阅-网络编程专栏httpsblogcsdnnetwhowincategory_12180345html"><strong>欢迎订阅 <a href="https://blog.csdn.net/whowin/category_12180345.html">『网络编程专栏』</a></strong></a></li>
      </ul>
    </li>
  </ul>
</nav>
  </div>
</div>
    <div class="post-content">
      <p>要在linux下的程序中获取gateway的IP地址，使用netlink是一种直接、可靠的方法，不需要依赖其它命令，直接从linux内核获取信息，netlink编程的中文资料很少，本文试图用尽可能简单的方式讨论使用netlink获取gataway的IP地址的编程方法，并有大量篇幅介绍netlink的相关数据结构和编程方法，阅读本文需要对linux下编程有一定了解，熟悉IPv4的socket编程；本文对网络编程的初学者有较大难度。</p>
<hr>
<blockquote>
<p>在linux编程的资料中，netlink编程的资料并不多，但netlink编程显然是本文无法越过的一道坎，所以下面需要用一定篇幅对netlink编程做个介绍；本文的最终目标是使用netlink这种与linux内核通信的机制，从内核获得路由表并从中找到gateway的IP地址。在具体实践中，获取路由表或者获取gateway的IP地址通常并不需要使用netlink编程实现，这种方法对应用层编程来说显得有些繁琐，本文主要还是作为netlink编程的一个范例，并以此为题介绍一些netlink的编程方法；有关其它获取gateway的IP地址的方法，请参见另一篇文章<a href="https://blog.csdn.net/whowin/article/details/129177942">《从proc文件系统中获取gateway的IP地址》</a>。</p>
</blockquote>
<h2 id="1-netlink-socket及netlink消息结构">1. netlink socket及netlink消息结构</h2>
<ul>
<li>
<p><strong>netlink socket</strong></p>
<ul>
<li>netlink是一个socket，所以它的编程与普通的socket编程类似，其socket的创建方法如下：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_NETLINK</span><span class="p">,</span> <span class="n">SOCK_DGRAM</span><span class="p">,</span> <span class="n">NETLINK_ROUTE</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>第一个参数可以是AF_NETLINK或者PF_NETLINK，表示要创建一个NETLINK socket，其实AF_NETLINK和PF_NETLINK是一样的，可以到 <code>bits/socket.h</code> 中查看相应的定义；</li>
<li>第二个参数可以是SOCK_RAW或者SOCK_DGRAM，linux内核的netlink子系统并不会区分SOCK_RAW和SOCK_DGRAM(内核5.15，也许更高的版本会区分)，所以使用SOCK_RAW和SOCK_DGRAM是一样的；</li>
<li>第三个参数是NETLINK协议，这些协议定义在文件 <code>linux/netlink.h</code> 中，在linux 5.15中定义了23种协议，协议数量最多为32个
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cp">#define NETLINK_ROUTE       0    </span><span class="cm">/* Routing/device hook                     */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_UNUSED      1    </span><span class="cm">/* Unused number                           */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_USERSOCK    2    </span><span class="cm">/* Reserved for user mode socket protocols */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_FIREWALL    3    </span><span class="cm">/* Unused number, formerly ip_queue        */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_SOCK_DIAG   4    </span><span class="cm">/* socket monitoring                       */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_NFLOG       5    </span><span class="cm">/* netfilter/iptables ULOG */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_XFRM        6    </span><span class="cm">/* ipsec */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_SELINUX     7    </span><span class="cm">/* SELinux event notifications */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_ISCSI       8    </span><span class="cm">/* Open-iSCSI */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_AUDIT       9    </span><span class="cm">/* auditing */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_FIB_LOOKUP  10    
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_CONNECTOR   11
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_NETFILTER   12    </span><span class="cm">/* netfilter subsystem */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_IP6_FW      13
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_DNRTMSG     14    </span><span class="cm">/* DECnet routing messages */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_KOBJECT_UEVENT  15    </span><span class="cm">/* Kernel messages to userspace */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_GENERIC     16
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="cm">/* leave room for NETLINK_DM (DM Events) */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_SCSITRANSPORT   18    </span><span class="cm">/* SCSI Transports */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_ECRYPTFS    19
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_RDMA        20
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_CRYPTO      21    </span><span class="cm">/* Crypto layer */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define NETLINK_SMC         22    </span><span class="cm">/* SMC monitoring */</span><span class="cp">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>尽管定义的协议很多，但常用的协议并不多，本例中仅使用<strong>NETLINK_ROUTE</strong>；</li>
<li>创建netlink socket，netlink协议使用<strong>NETLINK_ROUTE</strong>时，创建的socket又被称为<strong>rtnetlink</strong>，可以使用在线手册 <code>man 7 rtnetlink</code> 了解更详细的信息，本文仅讨论rtnetlink，以下将尽可能使用rtnetlink代替netlink socket。</li>
</ul>
<blockquote>
<p>netlink socket本质上是进程间的通信，一个用户进程使用netlink socket不仅可以和内核进程进行通信，两个(多个)用户进程间也可以使用netlink socket进行通信；</p>
</blockquote>
<blockquote>
<p>netlink除了使用内核定义的协议外，也可以使用自定义协议，在上面定义的netlink协议中的NETLINK_GENERIC就是用于用户自定义协议的，我们可以编写一个内核进程，然后在用户进程中使用NETLINK_GENERIC进行通信；</p>
</blockquote>
<blockquote>
<p>netlink socket有一个多播的特性，所谓多播，就是一个进程发出的消息可以有多个其它进程接收，netlink允许最多32个多播组，当向一个多播组发出消息时，所有已经加入这个多播组的进程都可以收到这个消息。</p>
</blockquote>
<blockquote>
<p>当使用rtnetlink与内核进行通信时，<strong>用户进程需要先向内核进程发出一个请求，然后接收内核进程返回的消息</strong>，从而实现与内核的通信；</p>
</blockquote>
<blockquote>
<p>netlink socket可以使用send、sendto、sendmsg发送消息，使用recv、recvfrom、recvmsg接收消息，这些和IPv4 socket是一样的；</p>
</blockquote>
</li>
<li>
<p><strong>netlink消息结构</strong></p>
<ul>
<li>
<p>netlink消息是由一个或多个netlink消息头和与其相关的payload组成的一个字节流</p>
<p><img src="https://whowin.gitee.io/images/180009/netlink-message-format.png" alt="netlink信息"></p>
</li>
</ul>
<hr>
<ul>
<li>netlink报头的结构为(struct nlmsghdr)，在头文件 <code>&lt;linux/netlink.h&gt;</code> 中定义，在下一节中会介绍了这个结构；</li>
<li>这个字节流只能使用一组标准的以NLMSG_开头的宏进行存取(netlink的手册中是这样说的)，这组宏定义在头文件linux/netlink.h中，可以使用在线手册<code>man 3 netlink</code>了解更多的信息，本文在后面章节也会简要介绍这些宏；</li>
<li>payload对不同的netlink协议和信息类型而言，其信息结构和长度都是不一样的，本文仅讨论NETLINK_ROUTE协议下的信息类型为RTM_GETROUTE下的消息结构，这个类型的payload结构为(struct rtmsg)，定义在头文件 <code>&lt;linux/rtnetlink.h&gt;</code> 中，这个结构也会在下一节介绍；详细信息可以查阅 <code>man 7 rtnetlink</code> ；</li>
<li>使用rtnetlink与内核通信时，需要首先建立一个rtnetlink，然后向这个rtnetlink发送一个请求消息，请求中说明想要的操作，然后从这个rtnetlink上接收内核的回应，解析回应信息获得结果；</li>
<li>在多部分消息中(一个字节流中包含多个netlink报头和相关payload)，第一个报头和后面所有的报头都会设置NLM_F_MULTI标志，最后一个报头的类型为NLMSG_DONE，表示多部分消息结束；有关NLM_F_MULTI和NLMSG_DONE的含义，下一节会做介绍。</li>
</ul>
</li>
</ul>
<h2 id="2-rtnetlink常用数据结构">2. rtnetlink常用数据结构</h2>
<blockquote>
<p>netlink编程涉及的各种结构非常多，这里仅介绍几个与本文讨论的话题相关的结构，但仍然会占很大的篇幅，这几个结构在第5、6节会大量用到，所以必须先介绍一下，<strong>可以先大致看一下</strong>，在阅读相应章节遇到具体结构时再回来仔细看。</p>
</blockquote>
<ol>
<li><strong>struct sockaddr_nl</strong> - 定义在头文件 <code>&lt;linux/netlink.h&gt;</code> 中</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sockaddr_nl</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">__kernel_sa_family_t</span>  <span class="n">nl_family</span><span class="p">;</span>  <span class="cm">/* AF_NETLINK  */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">short</span>        <span class="n">nl_pad</span><span class="p">;</span>     <span class="cm">/* zero        */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u32</span>                 <span class="n">nl_pid</span><span class="p">;</span>     <span class="cm">/* port ID     */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u32</span>                 <span class="n">nl_groups</span><span class="p">;</span>  <span class="cm">/* multicast groups mask */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>这个结构与IPv4 socket编程中的(struct sockaddr_in)的作用类似，(struct sockaddr_in)表示一个IPv4地址，这个结构表示一个netlink地址</li>
<li>nl_family字段与(struct sockaddr_in)中的sa_family一样，只是这里要填AF_NETLINK</li>
<li>nl_pad目前没有使用，填0即可；</li>
<li>nl_pid是当前进程的ID，可以使用getpid()获得；</li>
<li>nl_groups表示加入到那个多播组中，前面说过netlink最多允许32个多播组，这个字段每个bit代表一个多播组，为1表示加入这个多播组。</li>
</ul>
<ol start="2">
<li><strong>struct nlmsghdr</strong> - 定义在头文件 <code>&lt;linux/netlink.h&gt;</code> 中</li>
</ol>
<ul>
<li>这个结构是netlink报头的定义
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u32</span>   <span class="n">nlmsg_len</span><span class="p">;</span>    <span class="cm">/* Length of message including header */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u16</span>   <span class="n">nlmsg_type</span><span class="p">;</span>   <span class="cm">/* Message content */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u16</span>   <span class="n">nlmsg_flags</span><span class="p">;</span>  <span class="cm">/* Additional flags */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u32</span>   <span class="n">nlmsg_seq</span><span class="p">;</span>    <span class="cm">/* Sequence number */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u32</span>   <span class="n">nlmsg_pid</span><span class="p">;</span>    <span class="cm">/* Sending process port ID */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>nlmsg_len: netlink报文的长度，按4字节对齐；包括(struct nlmsghdr)的长度和后面payload的长度；</li>
<li>nlmsg_type: netlink报文的类型，不同的类型对应的netlink报文的结构也会不同，这些类型定义在头文件 <code>&lt;linux/rtnetlink.h&gt;</code> 中开头为&quot;RTM_&ldquo;的常数，本例中在发送netlink请求时类型为：RTM_GETROUTE，含义为从内核获取路由表，常用的常数有：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="n">NLMSG_NOOP</span>      <span class="mi">1</span>       <span class="err">无用，可忽略</span>
</span></span><span class="line"><span class="cl"><span class="n">NLMSG_ERROR</span>     <span class="mi">2</span>       <span class="err">出现错误</span>
</span></span><span class="line"><span class="cl"><span class="n">NLMSG_DONE</span>      <span class="mi">3</span>       <span class="err">数据输出结束</span>
</span></span><span class="line"><span class="cl"><span class="n">NLMSG_OVERRUN</span>   <span class="mi">4</span>       <span class="err">数据丢失</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">RTM_NEWROUTE</span>    <span class="mi">24</span>      <span class="err">新路由</span>
</span></span><span class="line"><span class="cl"><span class="n">RTM_GETROUTE</span>    <span class="mi">26</span>      <span class="err">从内核中获取路由表，发送</span><span class="n">netlink包请求时填写</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>nl_flags: 附加标志，在 <code>&lt;linux/netlink.h&gt;</code> 中定义，每一位代表一个标志，与本文相关的定义如下：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">NLM_F_REQUEST   0x01    这是一个请求信息
</span></span><span class="line"><span class="cl">NLM_F_MULTI     0x02    分片信息包的一部分，直到收到NLMSG_DONE结束
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">NLM_F_ROOT      0x100   整体返回数据，而不是一条一条返回
</span></span><span class="line"><span class="cl">NLM_F_MATCH     0x200   返回所有相符的数据
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">NLM_F_DUMP      (NLM_F_ROOT|NLM_F_MATCH)
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>nlmsg_seq: 序列号，信息的唯一性编号，可以自行编号，只要保证唯一性即可，通常可以使用时间戳；</li>
<li>nlmsg_pid: 发送方的pid，用于识别信息的归属，可以使用getpid()获得；</li>
<li>所有netlink报文的报头都是这个结构。</li>
</ul>
<ol start="3">
<li><strong>struct rtmsg</strong> - 定义在头文件linux/rtnetlink.h中</li>
</ol>
<ul>
<li>这个结构通常是紧跟在(struct nlmsghdr)后面的，当(struct nlmsghdr)中的nlmsg_type为 <strong>RTM_NEWROUTE</strong>、<strong>RTM_DELROUTE</strong>和<strong>RTM_GETROUTE</strong>时，(struct nlmsghdr)后面跟的数据才符合下面的结构，当nlmsg_type为其他值时，(struct nlmsghdr)后面跟的数据结构是不同的；
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">rtmsg</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_family</span><span class="p">;</span>   <span class="cm">/* Address family of route */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_dst_len</span><span class="p">;</span>  <span class="cm">/* Length of destination */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_src_len</span><span class="p">;</span>  <span class="cm">/* Length of source */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_tos</span><span class="p">;</span>      <span class="cm">/* TOS filter */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_table</span><span class="p">;</span>    <span class="cm">/* Routing table ID */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_protocol</span><span class="p">;</span> <span class="cm">/* Routing protocol */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_scope</span><span class="p">;</span>    <span class="cm">/* See below */</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">rtm_type</span><span class="p">;</span>     <span class="cm">/* See below */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span>  <span class="n">rtm_flags</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>以下仅讨论当(struct nlmsghdr)中的nlmsg_type为<strong>RTM_GETROUTE</strong>时(与本文例子相符)，该结构的情况</li>
<li>将rtm_dst_len和rtm_src_len设置为0表示想要获取指定路由表(routing table)中的所有项；</li>
<li>在发送netlink请求时，只需把rtm_family设为AF_INET，其它全部为0即可；</li>
<li>内核发回的回应中，rtm_dst_len、rtm_src_len、rtm_tos均没有意义可以不用管；</li>
<li>rtm_table表示当前路由表的类型，在linux/rtnetlink.h中有定义：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">rt_class_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_TABLE_UNSPEC</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* User defined values */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_TABLE_COMPAT</span><span class="o">=</span><span class="mi">252</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_TABLE_DEFAULT</span><span class="o">=</span><span class="mi">253</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_TABLE_MAIN</span><span class="o">=</span><span class="mi">254</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_TABLE_LOCAL</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_TABLE_MAX</span><span class="o">=</span><span class="mh">0xFFFFFFFF</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>RT_TABLE_UNSPEC表示是一个不明路由表；RT_TABLE_DEFAULT表示是一个默认路由表；RT_TABLE_MAIN表示是一个主路由表；RT_TABLE_LOCAL表示是一个本地路由表；在本例中，我们要得到的就是一个主路由表(RT_TABLE_MAIN)</li>
<li>rtm_protocol是路由协议，在 <code>&lt;linux/rtnetlink.h&gt;</code> 中有定义：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cp">#define RTPROT_UNSPEC     0     不明
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTPROT_REDIRECT   1     当前的IPv4下没有使用
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTPROT_KERNEL     2     路由由内核设置
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTPROT_BOOT       3     路由在启动时设置
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTPROT_STATIC     4     路由由管理员设置
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>在头文件 <code>&lt;linux/rtnetlink.h&gt;</code> 中专门有说明，当 rtm_protocol&gt;RTPROT_STATIC 时，Linux内核将不予理会，只能作为用户信息；在本例中，我们得到的路由表应该是由内核或者管理员设置的，当然也可以是在启动中由某个启动例程设置，所以 <strong>RTPROT_KERNEL</strong>、<strong>RTPROT_BOOT</strong>或者<strong>RTPROT_STATIC</strong> 都是可能的；</li>
<li>rtm_scope的值也是定义在 <code>&lt;linux/rtnetlink.h&gt;</code> 中，在这个头文件中说rtm_scope更像是一个到达目的地址的距离，它有下面几个可能的值：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">rt_scope_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_SCOPE_UNIVERSE</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>      <span class="cm">/* global route */</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* User defined values */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_SCOPE_SITE</span><span class="o">=</span><span class="mi">200</span><span class="p">,</span>        <span class="cm">/* interior route in the local autonomous system */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_SCOPE_LINK</span><span class="o">=</span><span class="mi">253</span><span class="p">,</span>        <span class="cm">/* route on this link */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_SCOPE_HOST</span><span class="o">=</span><span class="mi">254</span><span class="p">,</span>        <span class="cm">/* route on the local host */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RT_SCOPE_NOWHERE</span><span class="o">=</span><span class="mi">255</span>      <span class="cm">/* destination doesn&#39;t exist */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>其实这个值没有什么意义，本例中会返回RT_SCOPE_UNIVERSE，表示是一个全球路由；</li>
<li>rtm_type表示当前路由的类型(rtm_table表示路由表类型，和这个字段是不同的)，在 <code>&lt;linux/rtnetlink.h&gt;</code> 中定义：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">enum</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_UNSPEC</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_UNICAST</span><span class="p">,</span>      <span class="cm">/* Gateway or direct route */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_LOCAL</span><span class="p">,</span>        <span class="cm">/* Accept locally */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_BROADCAST</span><span class="p">,</span>    <span class="cm">/* Accept locally as broadcast,
</span></span></span><span class="line"><span class="cl"><span class="cm">                          send as broadcast */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_ANYCAST</span><span class="p">,</span>      <span class="cm">/* Accept locally as broadcast,
</span></span></span><span class="line"><span class="cl"><span class="cm">                          but send as unicast */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_MULTICAST</span><span class="p">,</span>    <span class="cm">/* Multicast route */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_BLACKHOLE</span><span class="p">,</span>    <span class="cm">/* Drop */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_UNREACHABLE</span><span class="p">,</span>  <span class="cm">/* Destination is unreachable */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_PROHIBIT</span><span class="p">,</span>     <span class="cm">/* Administratively prohibited */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_THROW</span><span class="p">,</span>        <span class="cm">/* Not in this table */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_NAT</span><span class="p">,</span>          <span class="cm">/* Translate this address */</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTN_XRESOLVE</span><span class="p">,</span>     <span class="cm">/* Use external resolver */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__RTN_MAX</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>本例中，我们要获取的是gateway IP，所以内核返回的rtm_type为1(RTN_UNICAST)</li>
<li>rtm_flags在本例中没有作用，其可能的值在 <code>&lt;linux/rtnetlink.h&gt;</code> 中定义：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cp">#define RTM_F_NOTIFY        0x100     </span><span class="cm">/* Notify user of route change */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTM_F_CLONED        0x200     </span><span class="cm">/* This route is cloned */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTM_F_EQUALIZE      0x400     </span><span class="cm">/* Multipath equalizer: NI */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTM_F_PREFIX        0x800     </span><span class="cm">/* Prefix addresses */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTM_F_LOOKUP_TABLE  0x1000    </span><span class="cm">/* set rtm_table to FIB lookup result */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#define RTM_F_FIB_MATCH     0x2000    </span><span class="cm">/* return full fib lookup match */</span><span class="cp">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<ol start="4">
<li><strong>struct rtattr</strong> - 定义在头文件 <code>&lt;linux/rtnetlink.h&gt;</code> 中</li>
</ol>
<ul>
<li>一个或多个(struct rtattr) + data将跟在(struct rtmsg)后面，data里表达着一个路由中的一项属性；
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">rtattr</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">short</span>  <span class="n">rta_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">short</span>  <span class="n">rta_type</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>rta_len字段表示(struct rtattr) + data的总长度，rta_type表示data中的数据类型，在 <code>&lt;linux/rtnetlink.h&gt;</code> 中定义：
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">rtattr_type_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_UNSPEC</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_DST</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_SRC</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_IIF</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_OIF</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_GATEWAY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_PRIORITY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_PREFSRC</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">......</span>
</span></span><span class="line"><span class="cl">    <span class="n">RTA_TABLE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">......</span>
</span></span><span class="line"><span class="cl">    <span class="n">__RTA_MAX</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>本例中只会用到前面几个定义，如果需要查看全部的rta_type的值，可以去 <code>&lt;linux/rtnetlink.h&gt;</code> 中去查找，以下仅就本文中用到的值做一下解释；</li>
<li><strong>RTA_UNSPEC</strong> 表示data数据可以忽略；</li>
<li><strong>RTA_DST</strong> 表示data中的数据为一个目的地址IP，data中数据结构为(struct in_addr)，一个32位16进制数字表示的IP地址；</li>
<li><strong>RTA_SRC</strong> 表示data中的数据为一个源地址IP，data中数据结构为(struct in_addr)，一个32位16进制数字表示的IP地址；</li>
<li><strong>RTA_IIF</strong> 表示data中的数据为输入接口索引(Input Interface Index)，data中数据为一个int；</li>
<li><strong>RTA_OIF</strong> 表示data中的数据为输出接口索引(Onput Interface Index)，data中数据为一个int；</li>
<li><strong>RTA_GATEWAY</strong> 表示data中的数据为gateway的IP地址，data中数据结构为(struct in_addr)，一个32位16进制数字表示的IP地址；</li>
<li><strong>RTA_PRIORITY</strong> 表示data中的数据为路由优先级，data中数据为一个int；</li>
<li><strong>RTA_PREFSRC</strong> 表示data中的数据为一个优先的源地址IP，data中数据结构为(struct in_addr)，一个32位16进制数字表示的IP地址；</li>
<li><strong>RTA_TABLE</strong> 表示data中的数据为路由表的ID，data中数据为一个int；</li>
<li>其实<strong>RTA_SRC</strong>和<strong>RTA_PREFSRC</strong>到底区别在哪里我也不知道，我以为会返回RTA_SRC，但在我的机器上实际返回的是RTA_PREFSRC；</li>
<li>本例中我们会遇到的rta_type有：RTA_DST、RTA_OIF、RTA_GATEWAY、RTA_PRIORITY、RTA_PREFSRC和RTA_TABLE。</li>
</ul>
<h2 id="3-netlink编程中常用的宏定义">3. netlink编程中常用的宏定义</h2>
<blockquote>
<p>按照netlink手册中的要求，对(struct nlmsghdr)的访问要使用一组标准的宏来完成，本节将简单介绍这组宏，也可以使用在线手册 <code>man 3 netlink</code> 来更多地了解这组宏；这些宏定义在头文件 <code>&lt;linux/netlink.h&gt;</code> 中，必要时可以查看代码理解其意义。</p>
</blockquote>
<blockquote>
<p>这些宏这样看上去会很枯燥，但在第5、6节和源代码中均会大量出现，可以先大致看一下，等看到相关章节遇到具体的宏时在回来仔细阅读。</p>
</blockquote>
<ul>
<li>
<p><strong>NLMSG_ALIGN(len)</strong></p>
<ul>
<li>返回len值按照4字节对齐后的值，比如len=5，则返回8；len=17，返回20；</li>
<li>将len按照适当的值进行字节对齐；所谓适当的值，指的是宏NLMSG_ALIGNTO定义的值，目前宏NLMSG_ALIGNTO为4。</li>
</ul>
</li>
<li>
<p><strong>NLMSG_LENGTH(len)</strong></p>
<ul>
<li>返回(struct nlmsghdr)按4字节对齐的长度 + len的值；</li>
<li>如果len是payload的长度，该宏返回的值应该和存放在(struct nlmsghdr)中的nlmsg_len字段的值一样</li>
</ul>
</li>
<li>
<p><strong>NLMSG_SPACE(len)</strong></p>
<ul>
<li>计算(struct nlmsghdr)按4字节对齐的长度 + len的值，返回这个值按4字节对齐后的结果；</li>
<li>返回payload长度为len的netlink消息占用的字节数。</li>
</ul>
</li>
<li>
<p><strong>NLMSG_DATA(nlh)</strong></p>
<ul>
<li>假定nlh为一个指向(struct nlmsghdr)的指针，该宏将返回(struct nlmsghdr)后面payload的指针。</li>
</ul>
</li>
<li>
<p><strong>NLMSG_NEXT(nlh, len)</strong></p>
<ul>
<li>在多部分消息中，收到的报文中会有多个(struct nlmsghdr) + payload，nlh是指向当前(struct nlmsghdr)的指针，len是从nlh开始到报文结束的总字节数，该宏返回指向下一个(struct nlmsghdr)的指针，同时修改len为返回的(struct nlmsghdr)指针到报文结束的总字符数。</li>
<li>应用程序在调用前需要自行检查当前的(struct nlmsghdr)是否设置了NLMSG_DONE，该宏不会检查该标志并返回NULL。</li>
</ul>
</li>
<li>
<p><strong>NLMSG_OK(nlh, len)</strong></p>
<ul>
<li>当nlh指向的(struct nlmsghdr)完好时会返回true，否则返回false；</li>
<li>nlh指向当前的(struct nlmsghdr)，len是从nlh开始到报文结束的总字节数；</li>
<li>(struct nlmsghdr)中有一个字段为nlmsg_len，该字段表示(struct nlmsghdr)和后面数据的总长度，</li>
<li>该宏要求符合下面三个逻辑：
<ol>
<li>len &gt; (struct nlmsghdr)的长度</li>
<li>(nlmsghdr).nlmsg_len &gt; (struct nlmsghdr)的长度</li>
<li>(nlmsghdr).nlmsg_len &lt; len</li>
</ol>
</li>
</ul>
</li>
<li>
<p><strong>NLMSG_PAYLOAD(nlh, len)</strong></p>
<ul>
<li>返回data的长度；</li>
<li>假定nlh指向一个(struct nlmsghdr)的指针，len为data在payload部分的偏移；</li>
<li>(struct nlmsghdr)中有一个字段为nlmsg_len，该字段表示(struct nlmsghdr)和后面数据的总长度；</li>
<li>该宏返回nlmsg_len - (struct nlmsghdr)长度 - len的值；</li>
<li>举例来说，当len=0时，该宏将返回payload的长度，这也是这个宏的一种常用的方式。</li>
</ul>
</li>
</ul>
<h2 id="4-rtnetlink编程中常用的宏">4. rtnetlink编程中常用的宏</h2>
<blockquote>
<p>在头文件 <code>&lt;linux/rtnetlink.h&gt;</code> 中定义了一组操作(struct rtmsg)的宏，以RTM_开头，只有两个；还定义了一组操作(struct rtattr)的宏，以RTA_开头，在这里简单介绍一下；使用在线手册 <code>man rtnetlink</code> 可以了解关于操作(struct rtattr)的宏的更详细的信息。</p>
</blockquote>
<blockquote>
<p>这些宏这样看上去会很枯燥，但在第5、6节和源代码中均会大量出现，可以先大致看一下，等看到相关章节遇到具体的宏时在回来仔细阅读。</p>
</blockquote>
<ul>
<li>
<p><strong>RTM_RTA(r)</strong></p>
<ul>
<li>返回在r后面指向(struct rtattr)的指针；</li>
<li>r为指向(struct rtmsg)的指针；</li>
<li>该宏认为(struct rtmsg)后面就是(struct rtattr)；</li>
</ul>
</li>
<li>
<p><strong>RTM_PAYLOAD(n)</strong></p>
<ul>
<li>返回(struct rtmsg)后面的payload的长度</li>
<li>n为一个指向(struct nlmsghdr)的指针</li>
<li>该宏认为报文的结构为(struct nlmsghdr) + (struct rtmsg) + payload</li>
<li>该宏认为(struct nlmsghdr)中的nlmsg_len为当前报文的总长度，减去(struct nlmsghdr)的长度，再减去(struct rtmsg)的长度就是payload的长度。</li>
</ul>
</li>
<li>
<p><strong>RTA_OK(rta, len)</strong></p>
<ul>
<li>当rta指向的(struct rtattr)完好时返回true，否则返回false；</li>
<li>rta指向当前(struct rtattr)的指针，len为(struct rtattr) + data的字节数，通常情况下，len不能为0；</li>
<li>该宏要求符合下面三个逻辑：
<ol>
<li>len &gt;= (struct rtattr)的长度</li>
<li>(rta).rta_len &gt;= (struct nlmsghdr)的长度</li>
<li>(rta).rta_len &lt;= len</li>
</ol>
</li>
</ul>
</li>
<li>
<p><strong>RTA_DATA(rta)</strong></p>
<ul>
<li>返回一个指向data的指针；rat为一个指向(struct rtattr)的指针；</li>
</ul>
</li>
<li>
<p><strong>RTA_PAYLOAD(rta)</strong></p>
<ul>
<li>返回data的长度；rat为一个指向(struct rtattr)的指针；</li>
</ul>
</li>
<li>
<p><strong>RTA_NEXT(rta, attrlen)</strong></p>
<ul>
<li>返回rta后面的下一个(struct rtattr)的指针；rta指向当前(struct rtattr)，attrlen为所有剩余(struct rtattr) + data的长度，初始值应该是所有(struct rtattr) + data的总长度；</li>
</ul>
</li>
<li>
<p><strong>RTA_LENGTH(len)</strong></p>
<ul>
<li>返回(struct rtattr)按4字节对齐的长度 + len的值；</li>
<li>当len为data的长度，该宏返回的值应该和(struct rtattr)中的rta_len一样；</li>
</ul>
</li>
<li>
<p><strong>RTA_SPACE(len)</strong></p>
<ul>
<li>计算(struct rtattr)按4字节对齐的长度 + len的值，返回这个值按4字节对齐后的结果；</li>
<li>返回data长度为len的(struct rtattr) + data占用的字节数。</li>
</ul>
</li>
</ul>
<h2 id="5-使用rtnetlink发送请求">5. 使用rtnetlink发送请求</h2>
<blockquote>
<p>前面讨论了一堆预备知识后，现在终于可以进行实际操作了；</p>
</blockquote>
<ol>
<li><strong>建立一个rtnetlink</strong></li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">nl_sock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">nl_sock</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_NETLINK</span><span class="p">,</span> <span class="n">SOCK_DGRAM</span><span class="p">,</span> <span class="n">NETLINK_ROUTE</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ol start="2">
<li><strong>构建netlink请求报文</strong></li>
</ol>
<ul>
<li>
<p>请求消息的结构比较简单，就两个结构：(struct nlmsghdr) + (struct rtmsg)</p>
<p><img src="https://whowin.gitee.io/images/180009/rtnetlink-request_message.png" alt="rtnetlink请求消息"></p>
<hr>
</li>
<li>
<p>下面代码构建了一个请求报文</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="o">*</span><span class="n">nl_msg_hdr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">rtmsg</span> <span class="o">*</span><span class="n">rt_msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">char</span> <span class="o">*</span><span class="n">msg_buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">msg_buf_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg_buf_len</span> <span class="o">=</span> <span class="n">NLMSG_SPACE</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">rtmsg</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="n">msg_buf</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">msg_buf_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">memset</span><span class="p">(</span><span class="n">msg_buf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">msg_buf_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nl_msg_hdr</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="o">*</span><span class="p">)</span><span class="n">msg_buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">rt_msg</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">rtmsg</span> <span class="o">*</span><span class="p">)</span><span class="n">NLMSG_DATA</span><span class="p">(</span><span class="n">nl_msg_hdr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nl_msg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_len</span>   <span class="o">=</span> <span class="n">msg_buf_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">nl_msg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_type</span>  <span class="o">=</span> <span class="n">RTM_GETROUTE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nl_msg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_flags</span> <span class="o">=</span> <span class="n">NLM_F_DUMP</span> <span class="o">|</span> <span class="n">NLM_F_REQUEST</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">nl_msg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_seq</span>   <span class="o">=</span> <span class="n">time</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">nl_msg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_pid</span>   <span class="o">=</span> <span class="n">getpid</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">rt_msg</span><span class="o">-&gt;</span><span class="n">rtm_family</span>  <span class="o">=</span> <span class="n">AF_INET</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>NLM_SPACE()是一个操作(struct nlmsghdr)的宏，前面有介绍；</p>
</li>
<li>
<p>(struct nlmsghdr)中的nlmsg_type=RTM_GETROUTE，通知linux内核要获取路由表，这个字段在介绍(struct nlmsghdr)时有介绍；</p>
</li>
<li>
<p>(struct nlmsghdr)中的nlmsg_flags是一个组合标志，在介绍(struct nlmsghdr)时有介绍；</p>
</li>
<li>
<p>NLM_F_REQUEST表示这条报文是一条请求报文；</p>
</li>
<li>
<p>NLM_F_DUMP是NLM_F_ROOT和NLM_F_MATCH的组合，表示需要<strong>所有</strong>符合条件的记录，所有记录一次性发送过来，而不是一条一条发；</p>
</li>
<li>
<p>nlmsg_seq使用时间戳是一种常用的方法，也可以自行编号，但要保证唯一性；</p>
</li>
<li>
<p>AF_INET表示只要IPv4的路由表。</p>
</li>
</ul>
<ol start="3">
<li><strong>发送netlink请求</strong></li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="n">send</span><span class="p">(</span><span class="n">nl_sock</span><span class="p">,</span> <span class="n">nl_msg_hdr</span><span class="p">,</span> <span class="n">nl_msg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_len</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="6-接收和解析内核返回的路由表">6. 接收和解析内核返回的路由表</h2>
<blockquote>
<p>通常的做法是先把所有的报文接收下来，然后再去解析，接收数据的方法和在IPv4下使用socket接收数据的方法是一样的。</p>
</blockquote>
<ol>
<li>
<p><strong>接收数据</strong></p>
<blockquote>
<p>接收数据之前并不知道有多少字节的数据需要接收，所以很难确定接收缓冲区的大小，所以最好是先检查一下socket上可以接收到多少字节的数据，然后给接收缓冲区分配内存，再去接收数据；</p>
</blockquote>
<blockquote>
<p>netlink返回的路由表的报文有点意思，路由表不会只有一条记录，所以这个返回的报文一定是个多部分消息，应该是由多个(struct nlmsghde) + payload组成，最后一个(struct nlmsghdr)中的nlmsg_type=NLMSG_DONE，表示整个路由表传送完毕，实际接收时发现要接收两次，第一次接收到的报文中，没有NLMSG_DONE消息，再接收一次，接收到的一个单独的NLMSG_DONE消息。</p>
</blockquote>
<blockquote>
<p>强调接收两次是因为我们首先要使用recv()测试一下有多少字节需要接收，然后为接收缓冲区分配内存，但是这个测试只能测试第一次要接收的报文长度，这个长度并不包括第二次接收时的NLMSG_DONE消息的长度，所以获得的字节数实际还要加上(struct nlmsghdr) + (struct rtmsg)的长度，最后加上的这部分就是NLMSG_DONE的消息长度，请看下面的代码。</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">char</span> <span class="o">*</span><span class="n">buf_ptr</span><span class="p">,</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">buf_size</span><span class="p">,</span> <span class="n">msg_len</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">read_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">buf_size</span> <span class="o">=</span> <span class="n">recv</span><span class="p">(</span><span class="n">nl_sock</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">MSG_PEEK</span><span class="o">|</span><span class="n">MSG_TRUNC</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="n">buf_size</span> <span class="o">+=</span> <span class="n">NLMSG_SPACE</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">rtmsg</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">buf_ptr</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">buf_size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">memset</span><span class="p">(</span><span class="n">buf_ptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">buf_size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">p</span> <span class="o">=</span> <span class="n">buf_ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">read_len</span> <span class="o">=</span> <span class="n">recv</span><span class="p">(</span><span class="n">nl_sock</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">buf_size</span> <span class="o">-</span> <span class="n">msg_len</span><span class="p">,</span> <span class="n">MSG_DONTWAIT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">read_len</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">errno</span> <span class="o">==</span> <span class="n">EAGAIN</span> <span class="o">||</span> <span class="n">errno</span> <span class="o">==</span> <span class="n">EWOULDBLOCK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// TODO:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// TODO:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">read_len</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// TODO:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">p</span> <span class="o">+=</span> <span class="n">read_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">msg_len</span> <span class="o">+=</span> <span class="n">read_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">msg_len</span> <span class="o">&lt;</span> <span class="n">buf_size</span> <span class="o">&amp;&amp;</span> <span class="n">read_len</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ol>
<ul>
<li><code>NLMSG_SPACE(struct rtmsg)</code> 这个宏计算了NLMSG_DONE这个报文所需要的空间；</li>
<li>recv()函数中的 <strong>MSG_PEEK|MSG_TRUNC</strong> 是一种固定用法，可以预知下一个报文有多少字节可以读取，但是并不会把这个报文标志为已读；</li>
<li>MSG_DONTWAIT使recv()执行时为无阻塞，按照 <code>man recv</code> 的说明，使用MSG_DONTWAIT时，如果recv()必须阻塞(意即无数据可读，需要等待在socket上)，则会返回错误，errno为EAGAIN或者EWOULDBLOCK，所以对这两个错误码一定要做判断；</li>
<li>实际读取数据时发现NLMSG_DONE报文中的(struct nlmsghdr)是完整的，但后面并没有完整的(struct rtmsg)，而且其中并没有找到有效的信息，可以忽略。</li>
</ul>
<ol start="2">
<li><strong>解析路由表报文</strong></li>
</ol>
<ul>
<li>
<p>路由表报文结构(多部分报文)</p>
<p><img src="https://whowin.gitee.io/images/180009/rtnetlink-routing-table.png" alt="rtnetlink routing table"></p>
</li>
</ul>
<hr>
<ul>
<li>
<p>多部分报文中，每一部分有一个(struct nlmsghdr)和一个(struct rtmsg)，在(struct rtmsg)后面有一个或多个(struct rtattr)，在(struct rtarrt)后面跟着数据，如下图，我们把这样一块数据叫做多部分报文中的一部分；</p>
<p><img src="https://whowin.gitee.io/images/180009/route-of-routing-table.png" alt="a route of routing table"></p>
</li>
</ul>
<hr>
<ul>
<li>
<p>多部分报文的每一部分表示一条记录，具体到路由表就是路由表中的每条记录将构成多部分报文的一部分；我们可以用命令 <code>cat /proc/net/route</code> 查看实际的路由表，这个路由表中有几条记录，那么linux内核就会返回几个部分，最后在加上一个NLMSG_DONE消息；</p>
</li>
<li>
<p>在每个部分中，(struct nlmsghdr)中的nlmsg_len定义了这一部分的总长度；(struct rtattr)中rta_len定义了(struct rtattr) + data的长度，rta_type定义了data中是什么数据，具体可以参阅前面有关数据结构的介绍；</p>
</li>
<li>
<p>在每个部分中，(struct rtmsg)用来说明这条记录的特征，比如协议族，是IPv4还是IPv6；路由表是主路由表还是本地路由表；这条路由是内核设置的还是启动过程中设置的；等等，因为这些特征不能在(struct nlmsghdr)中表示，在(struct rtattr)中表示也不合适；</p>
</li>
<li>
<p>我们需要的数据其实在data中，我们来举一个实际的例子来说明(struct rtattr) + data；假定要表达一个gateway的IP地址为192.168.0.1，则收到的数据如下(按16进制显示)：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">08 05 c0 a8 00 01
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>其中08是rta_len，表示(struct rtattr) + data的长度为8个字节，rta_type是05，05是RTA_GATEWAY，表示data中的数据为gateway的IP地址，后面的4个字节组成了一个32位的IP地址，其实就是192.168.0.1，0xc0就是十进制的192，0xa8就是十进制的168</p>
</li>
<li>
<p>所以我们在解析报文时应该分成两步，第一步按照(struct nlmsghdr)分开，这样分开的每一部分是路由表的一条记录；第二步是在一个部分中解析出每个结构下每个字段的数据</p>
</li>
<li>
<p>下面这段程序完成了第一步，并将每部分的数据交给函数parse_message去完成第二步</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="o">*</span><span class="n">nlmsg_hdr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">msg_buf_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nlmsg_hdr</span> <span class="o">=</span> <span class="n">buf_ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">msg_buf_len</span> <span class="o">=</span> <span class="n">msg_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(;</span> <span class="n">NLMSG_OK</span><span class="p">(</span><span class="n">nlmsg_hdr</span><span class="p">,</span> <span class="n">msg_buf_len</span><span class="p">);</span> <span class="n">nlmsg_hdr</span> <span class="o">=</span> <span class="n">NLMSG_NEXT</span><span class="p">(</span><span class="n">nlmsg_hdr</span><span class="p">,</span> <span class="n">msg_buf_len</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">nlmsg_hdr</span><span class="o">-&gt;</span><span class="n">nlmsg_type</span> <span class="o">==</span> <span class="n">NLMSG_DONE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">parse_message</span><span class="p">(</span><span class="n">nlmsg_hdr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// TODO: 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>这段程序中的几个宏：NLMSG_OK()、NLMSG_NEXT()和NLMSG_DONE在前面都有介绍；</p>
</li>
<li>
<p>下面这段程序，完成了上面这段程序中函数parse_message()的功能，将nlmsg_hdr指向多部分报文的其中一部分的(struct nlmsghdr)，便可以解析出所有(struct rtattr)下的数据，以本文讨论的话题而言，我们只需要主路由表中gateway的IP，所以其中增加了IPv4和主路由表的判断；</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">rtmsg</span> <span class="o">*</span><span class="n">rt_msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">rtattr</span> <span class="o">*</span><span class="n">rt_attr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">rt_len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">in_addr</span> <span class="n">dst_addr</span><span class="p">;</span>            <span class="c1">// destination IP address
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">struct</span> <span class="n">in_addr</span> <span class="n">src_addr</span><span class="p">;</span>            <span class="c1">// source IP address
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">struct</span> <span class="n">in_addr</span> <span class="n">gateway</span><span class="p">;</span>             <span class="c1">// gateway IP address
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">char</span> <span class="n">ifname</span><span class="p">[</span><span class="n">IF_NAMESIZE</span><span class="p">];</span>           <span class="c1">// network interface name
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">rt_msg</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">rtmsg</span> <span class="o">*</span><span class="p">)</span><span class="n">NLMSG_DATA</span><span class="p">(</span><span class="n">nlmsg_hdr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">((</span><span class="n">rt_msg</span><span class="o">-&gt;</span><span class="n">rtm_family</span> <span class="o">!=</span> <span class="n">AF_INET</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">rt_msg</span><span class="o">-&gt;</span><span class="n">rtm_table</span> <span class="o">!=</span> <span class="n">RT_TABLE_MAIN</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// return if it is not IPv4 or not main routing table.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rt_attr</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">rtattr</span> <span class="o">*</span><span class="p">)</span><span class="n">RTM_RTA</span><span class="p">(</span><span class="n">rt_msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">rt_len</span> <span class="o">=</span> <span class="n">RTM_PAYLOAD</span><span class="p">(</span><span class="n">nlmsg_hdr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(;</span> <span class="n">RTA_OK</span><span class="p">(</span><span class="n">rt_attr</span><span class="p">,</span> <span class="n">rt_len</span><span class="p">);</span> <span class="n">rt_attr</span> <span class="o">=</span> <span class="n">RTA_NEXT</span><span class="p">(</span><span class="n">rt_attr</span><span class="p">,</span> <span class="n">rt_len</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="n">rt_attr</span><span class="o">-&gt;</span><span class="n">rta_type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">RTA_OIF</span><span class="p">:</span>       <span class="c1">// rta_data is index of network interface. converter it to ifterface name here.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">if_indextoname</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span><span class="n">RTA_DATA</span><span class="p">(</span><span class="n">rt_attr</span><span class="p">),</span> <span class="n">ifname</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">RTA_GATEWAY</span><span class="p">:</span>   <span class="c1">// rta_date is gateway ip in 32bits(struct in_addr).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">gateway</span><span class="p">,</span> <span class="n">RTA_DATA</span><span class="p">(</span><span class="n">rt_attr</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">gateway</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">RTA_PREFSRC</span><span class="p">:</span>   <span class="c1">// Preferred source IP address in 32bits(struct in_addr)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">src_addr</span><span class="p">,</span> <span class="n">RTA_DATA</span><span class="p">(</span><span class="n">rt_attr</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">src_addr</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">RTA_DST</span><span class="p">:</span>       <span class="c1">// Destination IP address in 32 bits(struct in_addr)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dst_addr</span><span class="p">,</span> <span class="n">RTA_DATA</span><span class="p">(</span><span class="n">rt_attr</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dst_addr</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">RTA_TABLE</span><span class="p">:</span>     <span class="c1">// Routing table ID. 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="nl">RTA_PRIORITY</span><span class="p">:</span>  <span class="c1">// Priority of route.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">default</span><span class="o">:</span>            <span class="c1">// Unknown routing attribute
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>上面这段程序中获得的IP地址都是存放在一个(struct in_addr)中，熟悉IPv4下socket编程的程序员应该了解这是什么。</p>
</li>
</ul>
<h2 id="7-完整源代码">7. 完整源代码</h2>
<ul>
<li>
<p>源代码文件名：<a href="https://gitee.com/whowin/whowin/blob/blog/sourcecodes/180009/get-gateway-netlink.c">get-gateway-netlink.c</a>(<strong>点击文件名下载源程序</strong>)，里面有详细的注释</p>
</li>
<li>
<p>编译：<code>gcc -Wall get-gataway-netlink.c -o get-gateway-netlink</code></p>
</li>
<li>
<p>运行：<code>./get-gateway-netlink</code></p>
</li>
<li>
<p>带调试信息运行：<code>./get-gateway-netlink 1</code>，可以打印出大量的中间过程信息，对程序的理解将有很大帮助。</p>
</li>
<li>
<p>这个程序的主要部分在前面都已经讨论过了；</p>
</li>
<li>
<p>这个程序其实是可以获取整个路由表的，但程序中做了过滤，一旦找到gateway的IP地址便不再解析下面的信息，所以这个程序稍加修改可以获取整个路由表；</p>
</li>
<li>
<p>获取gateway IP地址的方法不止本文介绍的这一种方法，其实我认为netlink的方法尽管看上去比较&quot;高级&rdquo;，但也十分复杂和繁琐，最好的方法我认为是从proc文件系统中获取，想要了解这种方法的读者可以参考我的另一篇文章<a href="https://blog.csdn.net/whowin/article/details/129177942">《从proc文件系统中获取gateway的IP地址》</a>。</p>
</li>
</ul>
<h2 id="欢迎订阅-网络编程专栏httpsblogcsdnnetwhowincategory_12180345html"><strong>欢迎订阅 <a href="https://blog.csdn.net/whowin/category_12180345.html">『网络编程专栏』</a></strong></h2>
<hr>
<p><strong>欢迎访问我的博客：https://whowin.cn</strong></p>
<p><strong>email: <a href="mailto:hengch@163.com">hengch@163.com</a></strong></p>
<p><img src="https://whowin.gitee.io/images/qrcode/sponsor-qrcode.png" alt="donation"></p>
<!-- CSDN
[img01]:https://img-blog.csdnimg.cn/img_convert/3ae07ec8c1811bde08555be1530ddb05.png
[img02]:https://img-blog.csdnimg.cn/img_convert/10d396db0ad696c2ec0d0a8745cfaafd.png
[img03]:https://img-blog.csdnimg.cn/img_convert/d35277d37941f00fddee641a51557939.png
[img04]:https://img-blog.csdnimg.cn/img_convert/a0c34be7072ac1daccd73a0caa17c1f8.png
-->
<!--gitee
[article1]:https://whowin.gitee.io/post/blog/network/0008-get-gateway-ip-from-proc-filesys/
-->
    </div>

    <div class="post-copyright">
  <p class="copyright-item">
    <span class="item-title">文章作者</span>
    <span class="item-content">whowin</span>
  </p>
  <p class="copyright-item">
    <span class="item-title">上次更新</span>
    <span class="item-content">
        2023-02-04
        
    </span>
  </p>
  
  
</div>
<footer class="post-footer">
      <div class="post-tags">
          <a href="/tags/linux/">Linux</a>
          <a href="/tags/socket/">Socket</a>
          <a href="/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/">网络编程</a>
          <a href="/tags/netlink/">netlink</a>
          <a href="/tags/rtnetlink/">rtnetlink</a>
          </div>
      <nav class="post-nav">
        <a class="prev" href="/post/blog/network/0008-get-gateway-ip-from-proc-filesys/">
            <i class="iconfont icon-left"></i>
            <span class="prev-text nav-default">从proc文件系统中获取gateway的IP地址</span>
            <span class="prev-text nav-mobile">上一篇</span>
          </a>
        <a class="next" href="/post/blog/network/0007-recv-arp-request-and-reply/">
            <span class="next-text nav-default">接收arp请求并发送回应的实例</span>
            <span class="next-text nav-mobile">下一篇</span>
            <i class="iconfont icon-right"></i>
          </a>
      </nav>
    </footer>
  </article>
        </div>
        

  <span id="/post/blog/network/0009-get-gateway-ip-via-netlink/" class="leancloud_visitors" data-flag-title="linux下使用netlink获取gateway的IP地址">
		<span class="post-meta-item-text">文章阅读量 </span>
		<span class="leancloud-visitors-count">0</span>
		<p></p>
	  </span>
  <div id="vcomments"></div>
  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script src='//unpkg.com/valine/dist/Valine.min.js'></script>
  <script type="text/javascript">
    new Valine({
        el: '#vcomments' ,
        appId: 'OFCGzCfJRUglzOdzrqMGkbTR-gzGzoHsz',
        appKey: 'v7P29kPAEbsmaavaYPNhGhnF',
        notify:  false ,
        verify:  false ,
        avatar:'mm',
        placeholder: '说点什么吧...',
        visitor:  true 
    });
  </script>

  

      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="social-links">
      <a href="mailto:hengch@163.com" class="iconfont icon-email" title="email"></a>
  <a href="https://whowin.gitee.io/index.xml" type="application/rss+xml" class="iconfont icon-rss" title="rss"></a>
</div>
<div class="copyright">
  <span class="power-by">
    由 <a class="hexo-link" href="https://gohugo.io">Hugo</a> 强力驱动
  </span>
  <span class="division">|</span>
  <span class="theme-info">
    主题 - 
    <a class="theme-link" href="https://github.com/olOwOlo/hugo-theme-even">Even</a>
  </span>

  <div class="busuanzi-footer">
    <span id="busuanzi_container_site_pv"> 本站总访问量 <span id="busuanzi_value_site_pv"><img src="/img/spinner.svg" alt="spinner.svg"/></span> 次 </span>
      <span class="division">|</span>
    <span id="busuanzi_container_site_uv"> 本站总访客数 <span id="busuanzi_value_site_uv"><img src="/img/spinner.svg" alt="spinner.svg"/></span> 人 </span>
  </div>

  <span class="copyright-year">
    &copy; 
    2022 - 
    2024<span class="heart"><i class="iconfont icon-heart"></i></span><span>whowin</span>
  </span>
</div>

    </footer>

    <div class="back-to-top" id="back-to-top">
      <i class="iconfont icon-up"></i>
    </div>
  </div>
  
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/slideout@1.0.1/dist/slideout.min.js" integrity="sha256-t+zJ/g8/KXIJMjSVQdnibt4dlaDxc9zXr/9oNPeWqdg=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.1.20/dist/jquery.fancybox.min.js" integrity="sha256-XVLffZaxoWfGUEbdzuLi7pwaUJv1cecsQJQqGLe7axY=" crossorigin="anonymous"></script>



<script type="text/javascript" src="/js/main.min.64437849d125a2d603b3e71d6de5225d641a32d17168a58106e0b61852079683.js"></script>
  <script type="text/javascript">
    window.MathJax = {
      tex: {
        inlineMath: [['$','$'], ['\\(','\\)']],
        }
    };
  </script>
  <script async src="https://cdn.jsdelivr.net/npm/mathjax@3.0.5/es5/tex-mml-chtml.js" integrity="sha256-HGLuEfFcsUJGhvB8cQ8nr0gai9EucOOaIxFw7qxmd+w=" crossorigin="anonymous"></script>








</body>
</html>
